package net.xdclass.service.impl;

import cn.felord.payment.wechat.enumeration.TradeState;
import cn.felord.payment.wechat.v3.model.ResponseSignVerifyParams;
import cn.felord.payment.wechat.v3.model.TransactionConsumeData;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wechat.pay.contrib.apache.httpclient.notification.Notification;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
import lombok.extern.slf4j.Slf4j;
import net.xdclass.component.PayFactory;
import net.xdclass.config.RabbitMQConfig;
import net.xdclass.config.WechatPayConfig;
import net.xdclass.constant.TimeConstant;
import net.xdclass.controller.request.ConfirmOrderRequest;
import net.xdclass.controller.request.ProductOrderPageRequest;
import net.xdclass.enums.*;
import net.xdclass.exception.BizException;
import net.xdclass.interceptor.LoginInterceptor;
import net.xdclass.mapper.ProductOrderMapper;
import net.xdclass.model.EventMessage;
import net.xdclass.model.LoginUser;
import net.xdclass.model.ProductDO;
import net.xdclass.model.ProductOrderDO;
import net.xdclass.service.ProductOrderService;
import net.xdclass.service.ProductService;
import net.xdclass.util.IDUtil;
import net.xdclass.util.JsonData;
import net.xdclass.util.JsonUtil;
import net.xdclass.vo.PayInfoVO;
import org.apache.commons.lang3.StringUtils;
import org.junit.Assert;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author Allen
 * @version 1.0
 * @date 2022/11/21 17:19
 */
@Service
@Slf4j
public class ProductOrderServiceImpl extends ServiceImpl<ProductOrderMapper, ProductOrderDO> implements ProductOrderService {

	@Resource
	private ProductOrderMapper productOrderMapper;

	@Resource
	private ProductService productService;


	@Resource
	private RabbitTemplate rabbitTemplate;

	@Resource
	private RabbitMQConfig rabbitMQConfig;


	@Resource
	private PayFactory payFactory;


	@Resource
	private LockTemplate lockTemplate;

	@Resource
	private WechatPayConfig wechatPayConfig;

	@Resource
	private Verifier certificatesVerifier;

	@Override
	public Map<String, Object> pageByRequest(ProductOrderPageRequest orderPageRequest) {


		Page<ProductOrderDO> orderDOIPage = this.lambdaQuery()
			.eq(ProductOrderDO::getState, orderPageRequest.getState())
			.page(new Page<>(orderPageRequest.getPage(), orderPageRequest.getSize()));


		Map<String, Object> pageMap = new HashMap<>(3);
		pageMap.put("total_record", orderDOIPage.getTotal());
		pageMap.put("total_page", orderDOIPage.getPages());
		pageMap.put("current_data", orderDOIPage.getRecords());
		return pageMap;
	}

	@Override
	public String queryProductOrderState(String outTradeNo) {

		return this.lambdaQuery()
			.eq(ProductOrderDO::getOutTradeNo, outTradeNo)
			.one().getState();
	}

	@Override
	@Transactional
	public JsonData confirmOrder(ConfirmOrderRequest orderRequest) {

		LoginUser loginUser = LoginInterceptor.threadLocal.get();

		ProductDO productDO = productService.getById(orderRequest.getProductId());


		//验证价格
		this.checkPrice(productDO, orderRequest);

		String orderOutTradeNo = IDUtil.genSnowFlakeID().toString();

//		String snowflakeNextIdStr = IdUtil.getSnowflakeNextIdStr();

		//创建订单
		ProductOrderDO productOrderDO = this.saveProductOrder(orderRequest, loginUser, orderOutTradeNo, productDO);


		//创建支付对象
		PayInfoVO payInfoVO = PayInfoVO.builder()
			.accountNo(loginUser.getAccountNo())
			.outTradeNo(orderOutTradeNo)
			.clientType(orderRequest.getClientType())
			.payType(orderRequest.getPayType())
			.title(productDO.getTitle())
			.description("")
			.payFee(orderRequest.getPayAmount())
			.orderPayTimeoutMills(TimeConstant.ORDER_PAY_TIMEOUT_MILLS)
			.build();

		//发送延迟消息
		EventMessage eventMessage = EventMessage.builder()
			.eventMessageType(EventMessageType.PRODUCT_ORDER_NEW.name())
			.accountNo(loginUser.getAccountNo())
			.bizId(orderOutTradeNo)
			.build();

		rabbitTemplate.convertAndSend(rabbitMQConfig.getOrderEventExchange(), rabbitMQConfig.getOrderCloseDelayRoutingKey(), eventMessage);


		//调用支付信息
		String codeUrl = payFactory.pay(payInfoVO);
		if (StringUtils.isNotBlank(codeUrl)) {
			Map<String, String> resultMap = new HashMap<>(2);
			resultMap.put("code_url", codeUrl);
			resultMap.put("out_trade_no", payInfoVO.getOutTradeNo());
			return JsonData.buildSuccess(resultMap);
		}


		return JsonData.buildSuccess();
	}

	@Override
	public Boolean closeProductOrder(EventMessage eventMessage) {

		if (!eventMessage.getEventMessageType().equals(EventMessageType.PRODUCT_ORDER_NEW.name())) {
			log.error("消息类型错误");
			return Boolean.FALSE;
		}


		String outTradeNo = eventMessage.getBizId();
		Long accountNo = eventMessage.getAccountNo();


		ProductOrderDO productOrderDO = this.findByOutTradeNoAndAccountNo(outTradeNo, accountNo);
		if (ObjectUtil.isNull(productOrderDO)) {

			log.error("商品不存在");
			return Boolean.FALSE;
		}

		if (productOrderDO.getState().equals(ProductOrderStateEnum.NEW.name())) {
			log.info("订单{},超时，自动关单", productOrderDO.getId());

//			向第三方支付渠道查询支付状态，
//			1. 如果支付成功 报警
//			2. 如果支付失败 关单
//			this.updateProductOrderByState(productOrderDO, ProductOrderStateEnum.CANCEL.name());
			return Boolean.TRUE;
		}

		if (productOrderDO.getState().equals(ProductOrderStateEnum.PAY.name())) {
			log.info("订单{},已支付", productOrderDO.getId());
			return Boolean.TRUE;
		}


		return Boolean.FALSE;
	}

	@Override
	public Map<String, String> processCallBack(ResponseSignVerifyParams params, TransactionConsumeData data) {

//		验证签名
//		解密出铭文
//		修改订单状态
//		返回给微信响应


//		String clearBody = verifyAndParse(params);


		TradeState tradeState = data.getTradeState();
		String tradeStateName = tradeState.name();
		String outTradeNo = data.getOutTradeNo();

		String attach = data.getAttach();
		String accountNo = JSONUtil.parseObj(attach).get("accountNo", String.class);

		processWxchatCallBack(outTradeNo, tradeStateName, Long.valueOf(accountNo));

		HashMap<String, String> map = new HashMap<>();
		//响应微信
		map.put("code", "SUCCESS");
		map.put("message", "成功");
		return map;

	}

	@Override
	public void processWxchatCallBack(String outTradeNo, String tradeState, Long accountNo) {

		ProductOrderDO productOrderDO = this.findByOutTradeNoAndAccountNo(outTradeNo, accountNo);

		Map<String, Object> content = new HashMap<>(4);
		content.put("outTradeNo", outTradeNo);
		content.put("buyNum", productOrderDO.getBuyNum());
		content.put("accountNo", accountNo);
		content.put("product", productOrderDO.getProductSnapshot());

		//构建消息
		EventMessage eventMessage = EventMessage.builder()
			.bizId(outTradeNo)
			.accountNo(accountNo)
			.messageId(outTradeNo)
			.content(JsonUtil.obj2Json(content))
			.eventMessageType(EventMessageType.PRODUCT_ORDER_PAY.name())
			.build();

		if ("SUCCESS".equalsIgnoreCase(tradeState)) {

			//如果key不存在，则设置成功，返回true
			LockInfo lock = lockTemplate.lock(outTradeNo, 3 * 1000L * 60 * 60 * 24, 0L);


			if (ObjectUtil.isNotNull(lock)) {
				rabbitTemplate.convertAndSend(
					rabbitMQConfig.getOrderEventExchange(),
					rabbitMQConfig.getOrderUpdateTrafficRoutingKey(),
					eventMessage);

			}

		}

	}

	@Override
	public void handleProductOrderMessage(EventMessage eventMessage) {
		if (eventMessage.getEventMessageType().equals(EventMessageType.PRODUCT_ORDER_NEW.name())) {
			closeProductOrder(eventMessage);
		}
		if (eventMessage.getEventMessageType().equalsIgnoreCase(EventMessageType.PRODUCT_ORDER_PAY.name())) {
			//订单已经支付，更新订单状态
			String outTradeNo = eventMessage.getBizId();
			Long accountNo = eventMessage.getAccountNo();
			Boolean res = this.updateOrderPayState(outTradeNo, accountNo,
				ProductOrderStateEnum.NEW.name(),
				ProductOrderStateEnum.PAY.name());
			log.info("订单更新成功:eventMessage={}", eventMessage);
		}
	}

	private Boolean updateOrderPayState(String outTradeNo, Long accountNo, String oldState, String newState) {

		boolean update = this.lambdaUpdate().eq(ProductOrderDO::getOutTradeNo, outTradeNo)
			.eq(ProductOrderDO::getAccountNo, accountNo)
			.eq(ProductOrderDO::getState, oldState)
			.set(ProductOrderDO::getState, newState)
			.update();
		return update;
	}

	private String verifyAndParse(ResponseSignVerifyParams params) {

		try {


			NotificationRequest request = new NotificationRequest.Builder()
				.withSerialNumber(params.getWechatpaySerial())

				.withNonce(params.getWechatpayNonce())
				.withTimestamp(params.getWechatpayTimestamp())
				.withSignature(params.getWechatpaySignature())
				.withBody(params.getBody())
				.build();
			NotificationHandler handler = new NotificationHandler(certificatesVerifier,
				wechatPayConfig.getAppV3Secret().getBytes(StandardCharsets.UTF_8));
			// 验签和解析请求体
			Notification notification = handler.parse(request);
			Assert.assertNotNull(notification);
			log.info(notification.toString());
// 从notification中获取解密报文
			log.info(notification.getDecryptData());
			return notification.getDecryptData();
		} catch (Exception e) {
			log.error("解密失败：{}", e);
			throw new RuntimeException(e);

		}
	}

	private void updateProductOrderByState(ProductOrderDO productOrderDO, String state) {
		productOrderDO.setState(state);
		this.updateById(productOrderDO);

	}

	private ProductOrderDO findByOutTradeNoAndAccountNo(String outTradeNo, Long accountNo) {
		return this.lambdaQuery()
			.eq(ProductOrderDO::getOutTradeNo, outTradeNo)
			.eq(ProductOrderDO::getAccountNo, accountNo)
			.one();
	}

	private void checkPrice(ProductDO productDO, ConfirmOrderRequest orderRequest) {

		BigDecimal backendCalculatePrice = productDO.getAmount().multiply(BigDecimal.valueOf(orderRequest.getBuyNum()));

		if (backendCalculatePrice.compareTo(orderRequest.getTotalAmount()) != 0) {
			throw new BizException(BizCodeEnum.ORDER_CONFIRM_PRICE_FAIL);
		}
	}

	private ProductOrderDO getOneByProductId(Long productId) {

		return this.lambdaQuery()
			.eq(ProductOrderDO::getProductId, productId)
			.one();
	}


	private ProductOrderDO saveProductOrder(ConfirmOrderRequest orderRequest, LoginUser loginUser, String orderOutTradeNo, ProductDO productDO) {
		ProductOrderDO productOrderDO = new ProductOrderDO();

		//设置用户信息
		productOrderDO.setAccountNo(loginUser.getAccountNo());
		productOrderDO.setNickname(loginUser.getUsername());


		//设置商品信息
		productOrderDO.setProductId(productDO.getId());
		productOrderDO.setProductTitle(productDO.getTitle());
		productOrderDO.setProductSnapshot(JsonUtil.obj2Json(productDO));
		productOrderDO.setProductAmount(productDO.getAmount());

		//设置订单信息
		productOrderDO.setBuyNum(orderRequest.getBuyNum());
		productOrderDO.setOutTradeNo(orderOutTradeNo);
		productOrderDO.setCreateTime(new Date());
		productOrderDO.setDel(0);

		//发票信息
		productOrderDO.setBillType(BillTypeEnum.valueOf(orderRequest.getBillType()).name());
		productOrderDO.setBillHeader(orderRequest.getBillHeader());
		productOrderDO.setBillReceiverPhone(orderRequest.getBillReceiverPhone());
		productOrderDO.setBillReceiverEmail(orderRequest.getBillReceiverEmail());
		productOrderDO.setBillContent(orderRequest.getBillContent());


		//实际支付总价
		productOrderDO.setPayAmount(orderRequest.getPayAmount());
		//总价，没使用优惠券
		productOrderDO.setTotalAmount(orderRequest.getTotalAmount());
		//订单状态
		productOrderDO.setState(ProductOrderStateEnum.NEW.name());
		//支付类型
		productOrderDO.setPayType(ProductOrderPayTypeEnum.valueOf(orderRequest.getPayType()).name());

		//插入数据库
		this.save(productOrderDO);

		return productOrderDO;
	}
}
